/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package org.webos.java.jabber.tree;

import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import org.jivesoftware.smack.Chat;
import org.jivesoftware.smack.ChatManagerListener;
import org.jivesoftware.smack.ConnectionConfiguration;
import org.jivesoftware.smack.ConnectionListener;
import org.jivesoftware.smack.MessageListener;
import org.jivesoftware.smack.Roster;
import org.jivesoftware.smack.RosterEntry;
import org.jivesoftware.smack.RosterGroup;
import org.jivesoftware.smack.RosterListener;
import org.jivesoftware.smack.XMPPConnection;
import org.jivesoftware.smack.packet.Message;
import org.jivesoftware.smack.packet.Presence;
import org.jivesoftware.smack.proxy.ProxyInfo;
import org.json.JSONObject;
import org.webos.java.base.Frame;
import org.webos.java.base.tree.TreeController;
import org.webos.java.base.tree.TreeControllerItem;
import org.webos.java.jabber.JabberApp;
import org.webos.java.jabber.chat.ChatFrame;
import org.webos.java.jabber.chat.ChatMessage;
import org.webos.java.root.Log;
import org.webos.java.root.RandomGUID;

/**
 *
 * @author Kostya
 */
public class ConnectionItem extends TreeControllerItem<XMPPConnection, GroupItem> 
        implements ConnectionListener, RosterListener, MessageListener, ChatManagerListener{

    /**
     * @return the saveHistory
     */
    public boolean isSaveHistory() {
        return saveHistory;
    }

    /**
     * @return the appendMessages
     */
    public int getAppendMessages() {
        return appendMessages;
    }

    public boolean isShowNotify() {
        return showNotify;
    }

    public void setShowNotify(boolean showNotify) {
        this.showNotify = showNotify;
    }

    public void setSaveHistory(boolean saveHistory) {
        this.saveHistory = saveHistory;
    }

    public void setAppendMessages(int appendMessages) {
        this.appendMessages = appendMessages;
    }

    public enum CONN_STATE {DISCONNECTED, CONNECTING, CONNECTED};

    public static final int STATUS_OFFLINE = 0;
    public static final int STATUS_ONLINE = 1;
    public static final int STATUS_AWAY = 2;
    public static final int STATUS_XA = 3;
    public static final int STATUS_DND = 4;

    private boolean hideOffline = true;
    private boolean showNotify = true;
    private boolean saveHistory = true;
    private int appendMessages = 5;
    private String nickName = "";
    private String settingName = "";

    private CONN_STATE connState = CONN_STATE.DISCONNECTED;
    private Roster roster = null;
    private GroupItem general = null;
    private String userName = null;
    private String login = "";
    private String password = null;
    private String resource = null;
    private int currentStatus = 0;
    private String service = "";
    private boolean autoAway = false;
    private Map<String, Chat> chats = new HashMap<String, Chat>();
    private JabberApp jabberApp = null;
    private boolean connectionInProgress = false;

    public ConnectionItem(JabberApp app, TreeController aController, String aSettingName) {
        super(aController);
        jabberApp = app;
        settingName = aSettingName;
        setComparator(new GroupComparator());
        setId(RandomGUID.getGUID());
        setType(TYPE_FOLDER);
        setCollapsed(false);
        //Need to start connect and login

    }

    private void reinitConnection(){
        try {
            ConnectionConfiguration config = null;
            JSONObject conf = new JSONObject(jabberApp.getOption(settingName, "{}"));
            userName = conf.getString("username");
            login = userName.substring(0, userName.indexOf("@"));
            service = userName.substring(userName.indexOf("@")+1);
            Log.debug(this, "Username: "+login+", service: "+service);
            ProxyInfo proxyInfo = ProxyInfo.forNoProxy();
//            if(SystemManager.isProperty("proxy.enable", "yes")){
//                proxyInfo = ProxyInfo.forHttpProxy(SystemManager.getProperty("proxy.hostname", ""),
//                        Integer.parseInt(SystemManager.getProperty("proxy.port", "0")),
//                        SystemManager.getProperty("proxy.username", ""),
//                        SystemManager.getProperty("proxy.password", ""));
//            }
            if(conf.getBoolean("hostport")){
                Log.debug(this, "Using custom host/port", conf.getString("host"), conf.optInt("port", 0),
                        "proxy: "+proxyInfo.getProxyType());
                config = new ConnectionConfiguration(conf.getString("host"),
                        conf.optInt("port", 0), service, proxyInfo);
            }else{
                Log.debug(this, "Using direct connection");
                config = new ConnectionConfiguration(service, proxyInfo);
            }
            config.setCompressionEnabled(false);
//            config.setDebuggerEnabled(true);
            config.setExpiredCertificatesCheckEnabled(false);
            config.setNotMatchingDomainCheckEnabled(false);
            config.setReconnectionAllowed(true);
            config.setRosterLoadedAtLogin(true);
            config.setSASLAuthenticationEnabled(true);
            config.setSelfSignedCertificateEnabled(true);
            config.setVerifyChainEnabled(false);
            config.setVerifyRootCAEnabled(false);
            config.setSecurityMode(ConnectionConfiguration.SecurityMode.enabled);
            XMPPConnection conn = new XMPPConnection(config);
            password = jabberApp.getSecret(userName, "");
            autoAway = conf.optBoolean("autoaway", true);
            hideOffline = conf.optBoolean("hideoffline", true);
            nickName = conf.optString("name", "");
            appendMessages = conf.optInt("history_items", 5);
            saveHistory = conf.optBoolean("history", true);
            showNotify = conf.optBoolean("shownotify", true);
            setObject(conn);
        } catch (Throwable t) {
            Log.error(this, "Exception thrown: " + t.getMessage());
            t.printStackTrace();
        }
        if(null==nickName || "".equals(nickName.trim()))
            nickName = getUserName();
    }

    public Chat getChat(String aTo){
        Chat chat = chats.get(aTo);
        if(chat==null){
            chat = getObject().getChatManager().createChat(aTo, this);
            chats.put(aTo, chat);
        }
        return chat;
    }

    public void processMessage(Chat chat, Message message) {
        Log.debug(this, "processMessage called");
        if(null==message.getBody() || "".equals(message.getBody()))
            return;
        String from = userNameWoutService(message.getFrom());
        String entryName = UserItem.getEntryName(roster, from);
        Log.debug(this, "Got new message from: "+from+", "+message.getBody());
        int status = UserItem.getStatus(roster, from);
        ChatFrame frame = jabberApp.getChatFrame(from+getUserName(), this, entryName, from);
        if(frame!=null){
            frame.setIcon("jabber_icn jabber_"+status);
            ChatMessage chatMessage = new ChatMessage();
            chatMessage.setConnection(getId());
            chatMessage.setFrom(entryName);
            chatMessage.setIncoming(true);
            chatMessage.setMessage(message.getBody());
            chatMessage.setTo(getUserName());
            frame.newMessage(chatMessage, true);
        }
    }

    static String userNameWoutService(String aString){
        int index = aString.indexOf("/");
        if(index==-1)
            return aString;
        return aString.substring(0, index);
    }

    private void newChat(Chat aChat){
        String from = userNameWoutService(aChat.getParticipant());
        Log.debug(this, "newChat with "+from+", "+aChat.getThreadID());

        chats.put(from, aChat);
        aChat.addMessageListener(this);
    }

    public void chatCreated(Chat chat, boolean createdLocally) {
//        Log.debug(this, "chatCreated!!! "+chat.getParticipant(), chat.getThreadID());
        newChat(chat);
    }


    public int getStatus(){
        return currentStatus;
    }

    public boolean setStatus(int newStatus){
        if(currentStatus==STATUS_OFFLINE && newStatus!=STATUS_OFFLINE && !connectionInProgress){
            final ConnectionItem instance = this;
//            newStatus = currentStatus;
            final int futureStatus = newStatus;
            new Thread(new Runnable() {
                public void run() {
                    try {
                        reinitConnection();
                        if(getObject().isConnected()){
                            try {
                                getObject().disconnect();
                            } catch (Throwable t) {
                                Log.error(this, "Exception thrown: " + t.getMessage());
                                t.printStackTrace();
                            }
                        }
                        connectionInProgress = true;
                        treeController.getFrame().setTitle("Connecting...");
                        connState = CONN_STATE.CONNECTING;
                        getObject().connect();
                        getObject().addConnectionListener(instance);
                        getObject().getChatManager().addChatListener(instance);
                        getObject().login(login, password, resource);
                        connState = CONN_STATE.CONNECTED;
                        setStatus(futureStatus);
                        treeController.getFrame().setTitle(getUserName());
                        connectionCreated();
//                        setStatus(futureStatus);
                    } catch (Throwable t) {
                        Log.error(this, "Exception thrown: " + t.getMessage());
                        t.printStackTrace();
                        connState = CONN_STATE.DISCONNECTED;
                        treeController.getFrame().setTitle("Connection error");
                        setStatus(STATUS_OFFLINE);
                        jabberApp.sendError(811, "Can't connect to jabber server. Network error or invalid username/password provided");
                    }
                    connectionInProgress = false;
                    notifyModified();
                }
            }).start();
            return true;
        }
        try {
            switch (newStatus){
                case STATUS_OFFLINE:
                    if(getObject().isConnected()){
                        try {
                            getObject().sendPacket(new Presence(Presence.Type.unavailable));
                        } catch (Throwable t) {
                            Log.error(this, "Exception thrown: " + t.getMessage());
                            t.printStackTrace();
                        }
                        try {
                            getObject().disconnect();
                        } catch (Throwable t) {
                            Log.error(this, "Exception thrown: " + t.getMessage());
                            t.printStackTrace();
                        }
                        chats.clear();
                    }
                    break;
                case STATUS_ONLINE:
                    getObject().sendPacket(new Presence(Presence.Type.available, "", 0, Presence.Mode.available));
                    break;
                case STATUS_AWAY:
                    getObject().sendPacket(new Presence(Presence.Type.available, "", 0, Presence.Mode.away));
                    break;
                case STATUS_XA:
                    getObject().sendPacket(new Presence(Presence.Type.available, "", 0, Presence.Mode.xa));
                    break;
                case STATUS_DND:
                    getObject().sendPacket(new Presence(Presence.Type.available, "", 0, Presence.Mode.dnd));
                    break;
            }
            currentStatus = newStatus;
            notifyModified();
            return true;
        } catch (Throwable t) {
            Log.error(this, "Exception thrown: " + t.getMessage());
            t.printStackTrace();
        }
        return false;
    }

    public void moveEntry(UserItem aUserItem, GroupItem aGroupItem){
        GroupItem oldGroup = (GroupItem) aUserItem.getParent();
        if(oldGroup.equals(aGroupItem))
            return;
        try {
            if(oldGroup.getObject()!=null)
                oldGroup.getObject().removeEntry(aUserItem.getObject());
            if(aGroupItem.getObject()!=null)
                aGroupItem.getObject().addEntry(aUserItem.getObject());
        } catch (Throwable t) {
            Log.error(this, "Exception thrown: " + t.getMessage());
            t.printStackTrace();
        }
    }

    private void connectionCreated(){
        roster = getObject().getRoster();
        roster.setSubscriptionMode(Roster.SubscriptionMode.accept_all);
        roster.addRosterListener(this);
        general = new GroupItem(treeController, getId(), null, roster);
//        insertChild(0, general);
//        roster.
        treeController.setSilentMode();
        Log.debug(this, "Groups: "+roster.getGroupCount());
        for (RosterEntry rosterEntry : roster.getEntries()) {
            entryModified(rosterEntry, false);
        }
        treeController.sendTree();
        
    }

    public String getUserName(){
        return userName;
    }

    @Override
    public void objectTOJSON(JSONObject aObject) {
        aObject.putOpt("name", nickName);
        aObject.putOpt("icon_cls", "jabber_icn jabber_"+currentStatus);
    }

    private void entryModified(RosterEntry aEntry, boolean remove){

        int entryStatus = UserItem.getStatus(roster, aEntry);


        //Check all opened chats and change icon
        String chatCode = userNameWoutService(aEntry.getUser())+getUserName();
        for (int i = 0; i < jabberApp.getFramesCount(); i++) {
            Frame frame = jabberApp.getFrameAt(i);
//            Log.debug(this, "Looking for "+chatCode, frame.getCode());
            if(frame.getCode().equalsIgnoreCase(chatCode))
                frame.setIcon("jabber_icn jabber_"+entryStatus);
        }

        if(hideOffline && entryStatus==STATUS_OFFLINE)
            remove = true;
        //First, remove entry from groups
        for (int i = 0; i < getChildrenCount(); i++) {
            GroupItem groupItem = (GroupItem) children.get(i);
            if(groupItem.getObject()!=null && !aEntry.getGroups().contains(groupItem.getObject())){
                groupItem.entryModified(aEntry, true);
            }
            if(groupItem.getChildrenCount()==0){
                groupItem.remove();
                i--;
            }
        }

        if(aEntry.getGroups().size()==0){
            if(!remove)
                appendChild(general);
            general.entryModified(aEntry, remove);
        }else{
            general.entryModified(aEntry, true);
        }
        if(general.getChildrenCount()==0)
            general.remove();
        for (RosterGroup gr : aEntry.getGroups()) {
            GroupItem groupItem = null;
            for (TreeControllerItem grItem : children) {
                if(gr.equals(grItem.getObject())){
                    groupItem = (GroupItem) grItem;
                    groupItem.setObject(gr);
                    break;
                }
            }
            boolean newGroup = false;
            if(groupItem==null){
                newGroup = true;
                groupItem = new GroupItem(treeController, getId(), gr, roster);
//                appendChild(groupItem);
            }
            groupItem.entryModified(aEntry, remove);
            if(groupItem.getChildrenCount()==0 && !newGroup){
                groupItem.remove();
            }
            if(newGroup && groupItem.getChildrenCount()>0)
                appendChild(groupItem);
        }
    }

    public void connectionClosed() {
        connState = CONN_STATE.DISCONNECTED;
        setStatus(STATUS_OFFLINE);
        treeController.getFrame().setTitle("Disconnected");
        
    }

    public void connectionClosedOnError(Exception arg0) {
        connState = CONN_STATE.CONNECTING;
        setStatus(STATUS_OFFLINE);
        chats.clear();
    }

    public void reconnectingIn(int arg0) {
        
    }

    public void reconnectionSuccessful() {
        connState = CONN_STATE.CONNECTED;
        setStatus(STATUS_ONLINE);
        treeController.getFrame().setTitle(getUserName());
    }

    public void reconnectionFailed(Exception arg0) {
        
    }

    public void entriesAdded(Collection<String> arg0) {
        Log.debug(this, "Entries added: "+arg0.size());
        for (String string : arg0) {
            RosterEntry entry = roster.getEntry(string);
            if(entry!=null)
                entryModified(entry, false);
            else
                Log.debug(this, "entriesAdded: can't find user "+string);
        }
    }

    public void entriesUpdated(Collection<String> arg0) {
        Log.debug(this, "Entries updated: "+arg0.size());
        for (String string : arg0) {
            RosterEntry entry = roster.getEntry(string);
            if(entry!=null)
                entryModified(entry, false);
            else
                Log.debug(this, "entriesUpdated: can't find user "+string);
        }
    }

    public void entriesDeleted(Collection<String> arg0) {
        Log.debug(this, "Entries deleted: "+arg0.size());
        for (String string : arg0) {
            RosterEntry entry = roster.getEntry(string);
            if(entry!=null)
                entryModified(entry, true);
            else{
                Log.debug(this, "entriesDeleted: can't find user "+string);
                
            }
        }
    }

    public void presenceChanged(Presence arg0) {
        Log.debug(this, "presence: "+arg0.getFrom()+": "+arg0.getStatus()+", "+arg0.getType());
        String uName = arg0.getFrom();
        if(uName.indexOf("/")!=-1)
            uName = uName.substring(0, uName.indexOf("/"));
        RosterEntry entry = roster.getEntry(uName);
        if(entry!=null)
            entryModified(entry, false);
        else
            Log.debug(this, "presenceChanged: can't find user "+uName);
    }

    public void sessionOnline(){
        if(!autoAway || getStatus()!=STATUS_AWAY)
            return;
        setStatus(STATUS_ONLINE);
    }

    public void sessionOffline(){
        if(!autoAway || getStatus()!=STATUS_ONLINE)
            return;
        setStatus(STATUS_AWAY);
    }

    public Roster getRoster() {
        return roster;
    }

    public GroupItem getGeneralGroup() {
        return general;
    }

    public String getNickName() {
        return nickName;
    }

}
